﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using BLToolkit.Data;
using JetBrains.Annotations;
using Rsdn.Framework.Formatting;
using Rsdn.SmartApp;

namespace Rsdn.Janus
{
     /// <summary>
    /// Форматирование сообщения и расцветка кода для Rsdn@Home.
    /// </summary>
    [Service(typeof(IJanusFormatter))]
    internal class JanusFormatMessage : TextFormatter, IJanusFormatter
    {
        private readonly IServiceProvider _provider;
        [NotNull]
        private const string _defaultDomain = "rsdn.ru";

        // ReSharper disable ConvertToConstant, RedundantDefaultFieldInitializer
        [ExpectService]
        private IJanusDatabaseManager _dbManager = null;

        // ReSharper restore ConvertToConstant, RedundantDefaultFieldInitializer

        private readonly IDictionary<string, ProcessUrlItself> _hostFormatting =
            new Dictionary<string, ProcessUrlItself>(StringComparer.OrdinalIgnoreCase);

        private static readonly IDictionary<string, string> _mediaUrlFormat =
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        private static readonly Dictionary<Regex, string> _knownUrlsRegexDictionary =
            new Dictionary<Regex, string>();

        protected static readonly Regex _queryRx =
            new Regex(@"^\w+=", RegexOptions.Compiled);

        #region Constructors

        public JanusFormatMessage(IServiceProvider provider)
        {
            _provider = provider;
            this.AssignServices(provider);

            ProcessUrlItself handler =
                (match, addr, name) =>
                    ProcessMediaUrl(
                        provider,
                        match,
                        addr,
                        name);

            // youtube.com -----------------------------------------------------
            _hostFormatting["youtube.com"] = handler;
            _hostFormatting["www.youtube.com"] = handler;

            // rutube.ru   -----------------------------------------------------
            _hostFormatting["rutube.ru"] = handler;
            _hostFormatting["www.rutube.ru"] = handler;

            _hostFormatting["video.rutube.ru"] = handler;
            _hostFormatting["www.video.rutube.ru"] = handler;

            HostFormatting.Clear();
            SchemeFormatting.Clear();
        }

        /// <summary>
        /// Initializes the <see cref="JanusFormatMessage"/> class.
        /// </summary>
        static JanusFormatMessage()
        {
            // youtube.com -----------------------------------------------------
            _mediaUrlFormat["youtube.com"] = "http://www.youtube.com/v/{0}";
            _mediaUrlFormat["www.youtube.com"] = "http://www.youtube.com/v/{0}";

            // rutube.ru   -----------------------------------------------------
            _mediaUrlFormat["rutube.ru"] = "http://video.rutube.ru/{0}";
            _mediaUrlFormat["www.rutube.ru"] = "http://video.rutube.ru/{0}";

            // known hosts
            InitWellKnownUrlsDictionary();
        }

        /// <summary>
        /// Known host regulars expressions initialization.
        /// </summary>
        private static void InitWellKnownUrlsDictionary()
        {
            XmlDocument document = new XmlDocument();

            const string resourcePrefix = "Rsdn.Janus.Core.JanusProtocolSupport.";

            Stream xmlStream =
                Assembly.GetExecutingAssembly().GetRequiredResourceStream(resourcePrefix + "WellKnownUrls.xml");

            using (StreamReader sr = new StreamReader(xmlStream))
            {
                document.LoadXml(sr.ReadToEnd());
            }

            XmlNodeList nodes = document.SelectNodes("/WellKnownUrls/Url");
            if (nodes != null)
            {
                foreach (XmlNode node in nodes)
                {
                    Regex regex = new Regex(node.Attributes["Match"].InnerText, RegexOptions.Compiled);
                    string imagePath = node.Attributes["ImagePath"].InnerText;
                    _knownUrlsRegexDictionary.Add(regex, imagePath);
                }
            }
        }

        #endregion


        #region Overridden Methods

        private const string _defaultTemplate = @"<img border=""0"" src=""{0}"" />";

        private const string _surrogateTemplate =
            @"<img border=""0"" src=""{1}"" alt=""{2}"" onclick=""if(this.src != '{0}'){{this.src='{0}';this.alt='';return false;}}"">";

        public override string ProcessImages(Match match)
        {
            string url = match.Groups["url"].Value;

            if (Config.Instance.ForumDisplayConfig.ShowImagesAtImgTag)
            {
                return string.Format(_defaultTemplate, url);
            }

            string alt = url + "\r" + SR.AltTextShowImages;
            string path = ImagesHelper.GetImageUri(_provider, "surrogate", StyleImageType.ConstSize);

            return string.Format(_surrogateTemplate, url.Replace("'", @"\'"), path, alt);
        }

        /// <summary>
        /// Возвращает префикс для картинок.
        /// </summary>
        /// <returns>Строка префикса.</returns>
        protected override string GetImagePrefix()
        {
            //return JanusProtocolInfo.FormatURI(JanusProtocolResourceType.Image,
            //	@"ForumImages" + JanusProtocolInfo.ProtocolSeparatorChar);

            return JanusProtocolInfo.FormatURI(JanusProtocolResourceType.Formatter, String.Empty);
        }

        /// <summary>
        /// Обработка ссылок вида [#message_name]
        /// </summary>
        /// <param name="match">Результат сопоставления.</param>
        /// <returns>Отформатированный адрес сообщения.</returns>
        protected override string ProcessRsdnLink(Match match)
        {
            string name = match.Groups[1].Value;
            return InternalHelper.ProcessRsdnLinkInternal(_provider, name, name);
        }

        /// <summary>
        /// Обработка ссылок вида [url=...]...[/url]
        /// </summary>
        /// <returns>Обработанная ссылка.</returns>
        protected override string ProcessURLs(string url, string tag)
        {
            // Паттерн рег.выражения несколько отличается от ImplicitURLs,
            // в частности, отсутствует группа - scheme, hostname
            Match urlMatch = ParseUrl(url);
            return FormatURLs(urlMatch, url, tag);
        }

        /// <summary>
        /// Обработка ссылок определенные при парсинге автоматически, т.е. без явного указания. 
        /// </summary>
        /// <param name="urlMatch">Результат регулярного выражения.</param>
        /// <returns>Обработанная ссылка.</returns>
        protected override string ProcessImplicitURLs(Match urlMatch)
        {
            return FormatURLs(urlMatch, urlMatch.Value, urlMatch.Value);
        }

        /// <summary>
        /// Formats the URls.
        /// Convert to HTML represnetation
        /// </summary>
        /// <param name="urlMatch">The URL match.</param>
        /// <param name="urlAddress">The URL address.</param>
        /// <param name="urlName">Name of the URL.</param>
        /// <returns></returns>
        protected override string FormatURLs(Match urlMatch, string urlAddress, string urlName)
        {
            if (!string.IsNullOrEmpty(urlAddress))
            {
                if (!UrlHelper.IsSchemePresent(urlMatch))
                {
                    string domain = string.Empty;
                    //if (!UrlHelper.IsDomainPresent(urlMatch, urlAddress))
                    if (!UrlHelper.IsDomainPresent(urlAddress))
                    {
                        domain = DefaultDomain;
                    }

                    urlAddress = "http://" + domain + urlAddress;
                }

                urlAddress = urlAddress.EncodeAgainstXSS();
            }

            string hostname = UrlHelper.GetHostname(urlMatch);
            
            ProcessUrlItself processUrl;

            if (_hostFormatting.TryGetValue(hostname, out processUrl))
            {
                return processUrl(urlMatch, urlAddress, urlName);
            }

            return ProcessUrlInternal(urlAddress, urlName);
        }

        #endregion

        #region Private Methods


        /// <summary>
        /// Processes the URL internal.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        private string ProcessUrlInternal(string url, string text)
        {
            if (url == null) throw new ArgumentNullException("url");
            if (text == null) throw new ArgumentNullException("text");

            JanusProtocolInfo info = JanusProtocolInfo.Parse(url);

            string title;
            string imageUrl;
            LinkType linkType;

            if (info != null && info.ResourceType == JanusProtocolResourceType.Faq)
            {
                return InternalHelper.ProcessRsdnLinkInternal(_provider, info.Parameters, text);
            }

            url = RefineUrl(info, out imageUrl, out title, out linkType) ?? url;

            if (linkType == LinkType.External)
            {
                imageUrl = RefineImageForWellKnownUrls(_provider, url) ?? imageUrl;
            }

            const string format =
                "<a class='m' href='{0}{1}' title='{5}'>" +
                "<img border='0' align='absbottom' src='{3}' style='margin-bottom:1px;margin-right: 2px;'>" +
                "</a>" +
                "<a class='m' href='{0}{1}' title='{5}'>{2}</a>{4}";

            string linkStart = UrlHelper.IsSchemePresent(url) ? "" : "http://";
            string linkEnd = url.EncodeAgainstXSS();
            string imagePath = InternalHelper.GetPostfixImagePath(_provider, linkType, info);
            string titleText = string.IsNullOrEmpty(title) ? url : title;
            //                              0         1       2      3         4        5
            return string.Format(format, linkStart, linkEnd, text, imageUrl, imagePath, titleText);
        }

        /// <summary>
        /// Processes the media URL.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="match">The match.</param>
        /// <param name="url">The URL.</param>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        private static string ProcessMediaUrl(
            IServiceProvider provider,
            Match match,
            string url,
            string text)
        {
            // параметр
            string param = _queryRx.Replace(match.Groups[match.Groups.Count - 1].Value, "");

            string mediaSrc;

            _mediaUrlFormat.TryGetValue(UrlHelper.GetHostname(match), out mediaSrc);

            mediaSrc = param.Length != 0
                ? string.Format(mediaSrc ?? string.Empty, param)
                : url;

            const string format =
                "<span><a mediaSrc='{4}' class='media' href='{0}' title='{3}'>{1}" +
                "<img border='0' align='baseline' src='{2}'/></a></span>";

            string imageUri = ImagesHelper.GetImageUri(provider, @"ForumImages\tv", StyleImageType.ConstSize);
            //                            0     1     2       3      4
            return string.Format(format, url, text, imageUri, url, mediaSrc);
        }

        /// <summary>
        /// Возвращает в случае возможности линк на внутренний ресурс 
        /// (message, user-info), иначе - оригинальный линк. 
        /// Если <paramref name="protocolInfo"/>
        /// есть <c>null</c>, то функция возвращает <c>null</c>.
        /// </summary>
        /// <param name="protocolInfo">Информация о протоколе 
        /// <see cref="Rsdn.Janus.JanusProtocolInfo"/>.</param>
        /// <param name="imageUrl">Ссылка на иконку адреса.</param>
        /// <param name="title">Всплывающая подсказка (тултип).</param>
        /// <param name="linkType">Тип ссылки <see cref="Rsdn.Janus.LinkType"/>.</param>
        /// <returns>
        /// В случае возможности линк на внутренний ресурс
        /// (message, user-info), иначе - оригинальный линк. 
        /// Если <paramref name="protocolInfo"/>
        /// есть <c>null</c>, то результатом тоже будет <c>null</c>.
        /// </returns>
        private string RefineUrl(
            JanusProtocolInfo protocolInfo,
            out string imageUrl, out string title, out LinkType linkType)
        {
            title = string.Empty;
            linkType = LinkType.External;
            imageUrl = ImagesHelper.GetImageUri(_provider, @"ForumImages\webref", StyleImageType.ConstSize);

            if (protocolInfo == null)
            {
                return null;
            }

            switch (protocolInfo.ResourceType)
            {
                case JanusProtocolResourceType.Message:
                case JanusProtocolResourceType.MessageAbsent:
					using (var db = _dbManager.CreateDBContext())
                    {
                        if (!protocolInfo.IsId)
                            return protocolInfo.OriginalUrl;

                        var msg =
                            db
                                .Message(
                                    protocolInfo.Id,
                                    m =>
                                        new
                                        {
                                            m.IsRead,
                                            m.IsMarked,
                                            m.ArticleId,
                                            m.Subject,
                                            m.UserNick,
                                            m.Date
                                        });

                        // сообщение не найдено
                        if (msg == null)
                        {
                            title = SR.MessageAbsent;
                            linkType = LinkType.Absent;
                            imageUrl = ImagesHelper.GetImageUri(_provider, "MsgNotInDb", StyleImageType.ConstSize);

                            return protocolInfo.OriginalUrl;
                        }

                        IFormatMessageHelperService formatMessageService =
                            _provider.GetRequiredService<IFormatMessageHelperService>();

                        // сообщение найдено
                        title = formatMessageService.FormatMsgLinkTitle(msg.Subject, msg.Date, msg.UserNick);

                        linkType = LinkType.Local;
                        imageUrl = ImagesHelper.GetMessageImagePath(
                            _provider,
                            msg.IsRead,
                            msg.IsMarked,
                            msg.ArticleId > 0);
                    }

                    return protocolInfo.Url;

                case JanusProtocolResourceType.UserInfo:
					using (var db = _dbManager.CreateDBContext())
                    {
                        var user =
                            protocolInfo.IsId
                                ? db.User(
                                    protocolInfo.Id,
                                    u => new { u.UserClass, DisplayName = u.DisplayName() })
                                : null;

                        if (user == null)
                        {
                            title = SR.UserInfoMissed;
                            imageUrl = ImagesHelper.GetImageUri(_provider, "UserNotInDb", StyleImageType.ConstSize);
                            return protocolInfo.OriginalUrl;
                        }

                        imageUrl = ImagesHelper.GetUserImagePath(_provider, user.UserClass);
                        title = user.DisplayName;
                    }
                    return protocolInfo.Url;

                default:
                    imageUrl = ImagesHelper.GetResourceImagePath(_provider, protocolInfo.ResourceType);
                    break;
            }

            return protocolInfo.OriginalUrl;
        }

        /// <summary>
        /// Изменяет картинку для известных адресов.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="url">Url</param>
        private static string RefineImageForWellKnownUrls(
            IServiceProvider provider,
            string url)
        {
            return
                _knownUrlsRegexDictionary
                    .Where(item => item.Key.IsMatch(url))
                    .Select(
                        item => ImagesHelper.GetImageUri(provider, @"ForumImages\" + item.Value, StyleImageType.ConstSize))
                    .FirstOrDefault();
        }

        #endregion
        public string DefaultDomain
        {
            get { return _defaultDomain; }
        }

        public static class InternalHelper
        {
            public const string _imageFormatPostfix =
                @"&nbsp;<a href='{0}'><img src='{1}' align='absmiddle' border='0' title='{2}'></a>";

            /// <summary>
            /// Хвост (теги &lt;a&gt; и &lt;img&gt;) для ссылок
            /// на информацию о пользователе на сайте.
            /// </summary>
            /// <param name="provider">The provider.</param>
            /// <param name="uid">ID пользователя.</param>
            /// <returns>Ссылка.</returns>
            [NotNull]
            public static string GetUserLinkPostfix(IServiceProvider provider, int uid)
            {
                string userProfileUrl = SiteUrlHelper.GetUserProfileUrl(uid);

                string imageUri = ImagesHelper.GetImageUri(provider, "ExtBrowser", StyleImageType.Small);

                return String.Format(_imageFormatPostfix, userProfileUrl, imageUri, SR.MsgExtBrowserLinkTitle);
            }

            /// <summary>
            /// Хвост (теги &lt;a&gt; и &lt;img&gt;) для локальных ссылок.
            /// </summary>
            /// <param name="provider">The provider.</param>
            /// <param name="mid">ID сообщения.</param>
            /// <returns>The get msg in db link postfix.</returns>
            public static string GetMsgInDbLinkPostfix(IServiceProvider provider, int mid)
            {
                string messageUrl = SiteUrlHelper.GetMessageUrl(mid);

                string imageUri = ImagesHelper.GetImageUri(provider, "ExtBrowser", StyleImageType.Small);

                return String.Format(_imageFormatPostfix, messageUrl, imageUri, SR.MsgExtBrowserLinkTitle);
            }

            /// <summary>
            /// Хвост (теги &lt;a&gt; и &lt;img&gt;) для локальных, но отсутсвующих в базе ссылок.
            /// </summary>
            /// <param name="provider">The provider.</param>
            /// <param name="mid">ID сообщения.</param>
            /// <returns>The get msg absent link load postfix.</returns>
            [NotNull]
            public static string GetMsgAbsentLinkLoadPostfix(IServiceProvider provider, int mid)
            {
                string absentMessageUrl = 
                    UrlHelper.FormatUri(JanusProtocolResourceType.MessageAbsent, mid.ToString());

                string downloadTopicImg = ImagesHelper.GetImageUri(provider, "downloadtopic", StyleImageType.Small);

                return String.Format(_imageFormatPostfix, absentMessageUrl, downloadTopicImg, SR.MessageAbsentLoadTitle);
            }

            /// <summary>
            /// Хвост для ссылок (линк на внешний ресурс) на сообщения и информации
            /// о пользователе. Для остальных случаев возвращает пустую строку.
            /// </summary>
            /// <param name="provider">The provider.</param>
            /// <param name="linkType">Тип ссылки.</param>
            /// <param name="info">Информация о протоколе.</param>
            /// <returns>Хвост для ссылок.</returns>
            public static string GetPostfixImagePath(IServiceProvider provider, LinkType linkType, [CanBeNull] JanusProtocolInfo info)
            {
                if (info != null)
                {
                    if (linkType == LinkType.Local)
                    {
                        return GetMsgInDbLinkPostfix(provider, info.Id);
                    }

                    if (linkType == LinkType.Absent)
                    {
                        return GetMsgAbsentLinkLoadPostfix(provider, info.Id);
                    }

                    if (info.ResourceType == JanusProtocolResourceType.UserInfo)
                    {
                        return GetUserLinkPostfix(provider, info.Id);
                    }
                }

                return String.Empty;
            }

            /// <summary>
            /// Processes the RSDN link internal.
            /// </summary>
            /// <param name="provider">The provider.</param>
            /// <param name="name">The name.</param>
            /// <param name="text">The text.</param>
            /// <returns></returns>
            public static string ProcessRsdnLinkInternal(IServiceProvider provider, string name, string text)
            {
                string link;
                string imageUrl;

                IFormatMessageHelperService formatMessageService =
                    provider.GetRequiredService<IFormatMessageHelperService>();

                var msg =
                    string.IsNullOrEmpty(name)
                        ? null
                        : DatabaseManager
                              .GetMessageByName(
                                  provider,
                                  name,
                                  m => new { m.ID, m.Subject, m.Date, m.UserNick });
                if (msg == null)
                {
                    link = SiteUrlHelper.GetInfoUrl(name);
                    imageUrl = ImagesHelper.GetImageUri(provider, @"ForumImages\webref", StyleImageType.ConstSize);
                }
                else
                {
                    link = UrlHelper.FormatUri(JanusProtocolResourceType.Message, msg.ID.ToString());
                    imageUrl = ImagesHelper.GetImageUri(provider, "NameGroup", StyleImageType.ConstSize);
                }

                const string format =
                    "<a class='m' href='{0}' title='{4}'><img border='0' align='absmiddle' src='{1}'></a>" +
                    "&nbsp;<a class='m' href='{0}' title='{4}'>{2}</a>{3}";

                string args = msg != null ? JanusFormatMessage.InternalHelper.GetMsgInDbLinkPostfix(provider, msg.ID) : string.Empty;
                string title = msg != null
                                   ? formatMessageService.FormatMsgLinkTitle(msg.Subject, msg.Date, msg.UserNick)
                                   : link;
                //                             0       1      2     3     4
                return string.Format(format, link, imageUrl, text, args, title);
            }

        }
    }
}
